package com.thornbird.vehiclerecycling.config;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.thornbird.vehiclerecycling.model.record.UserRecord;
import com.thornbird.vehiclerecycling.service.UserService;
import com.thornbird.vehiclerecycling.util.DateUtil;
import com.thornbird.vehiclerecycling.util.JsonUtil;
import com.thornbird.vehiclerecycling.util.ResultUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.SimpleDateFormat;
import java.util.*;

@Component
@Configuration
public class WebConfig implements WebMvcConfigurer {

    public static final int AUTO_GROW_COLLECTION_LIMIT = 3000;

    @Autowired
    private WebApplicationContext applicationContext;

    @Value("${spring.jackson.date-format}")
    private String datePattern;

    @Value("${spring.jackson.time-zone}")
    private String timeZoneID;

    private static List<String> apiUriList;

    private static List<String> apiUriList2;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new CurrentUserMethodArgumentResolver());
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new ApiNotFoundInterceptor()).addPathPatterns("/api/**");
        registry.addInterceptor(new UserActiveInterceptor()).addPathPatterns("/", "/page/**", "/api/**")
                .excludePathPatterns("/page/public/**");
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations(
                "classpath:/META-INF/resources/", "classpath:/favicon.ico", "classpath:/manifest.json",
                "classpath:/public/", "classpath:/static/", "classpath:/templates/");
    }

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.clear();
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = converter.getObjectMapper();
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.setDateFormat(new SimpleDateFormat(datePattern));
        objectMapper.setTimeZone(TimeZone.getTimeZone(timeZoneID));
        converter.setObjectMapper(objectMapper);
        converters.add(0, converter);
    }

    @Autowired
    public void setWebBindingInitializer(RequestMappingHandlerAdapter requestMappingHandlerAdapter) {
        requestMappingHandlerAdapter.setWebBindingInitializer(new DateWebBindingInitializer());
    }


    class ApiNotFoundInterceptor implements HandlerInterceptor {

        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
                throws Exception {
            boolean result = true;
            createApiUrlList();
            String uri = request.getRequestURI().toLowerCase();
            String uri2 = null;
            String[] parts = uri.split("/");
            if (parts.length > 2) {
                uri2 = getUri2(parts);
            }
            if (apiUriList.indexOf(uri) < 0) {
                if (uri2 == null) {
                    result = false;
                } else if (apiUriList2.indexOf(uri2) < 0) {
                    result = false;
                }
            }
            if (!result) {
                response.setHeader("Content-Type", "application/json");
                response.getOutputStream().print(JsonUtil.toJsonString(ResultUtil.notFoundError()));
                response.getOutputStream().close();
            }
            return result;
        }

        private void createApiUrlList() {
            if (apiUriList != null) {
                return;
            }

            RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
            Map<RequestMappingInfo, HandlerMethod> methodMap = mapping.getHandlerMethods();
            apiUriList = new ArrayList<>();
            apiUriList2 = new ArrayList<>();
            for (RequestMappingInfo info : methodMap.keySet()) {
                Set<String> uris = info.getPatternsCondition().getPatterns();
                for (String uri : uris) {
                    String[] parts = uri.split("/");
                    if (parts.length > 1 && parts[1].toLowerCase().equals("api")) {
                        String lastPart = parts[parts.length - 1];
                        if (lastPart.startsWith("{") && lastPart.endsWith("}")) {
                            apiUriList2.add(getUri2(parts));
                        }
                        apiUriList.add(uri.toLowerCase());
                    }
                }
            }
        }

        private String getUri2(String[] parts) {
            String[] parts2 = new String[parts.length - 1];
            for (int i = 0; i < parts.length - 1; i++) {
                parts2[i] = parts[i];
            }
            return String.join("/", parts2).toLowerCase();
        }

    }


    class UserActiveInterceptor implements HandlerInterceptor {

        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
                throws Exception {
            UserRecord userRecord = getUserRecord(request);
            if (userRecord != null && userRecord.getActive().booleanValue()) {
                return true;
            }

            String uri = request.getRequestURI().toLowerCase();
            if (uri.equals("/api/ping") || uri.equals("/api/currentuser")) {
                return true;
            }

            if (uri.startsWith("/api/")) {
                response.setHeader("Content-Type", "application/json");
                response.getOutputStream().print(JsonUtil.toJsonString(ResultUtil.forbiddenError()));
                response.getOutputStream().close();
            } else {
                response.sendError(HttpStatus.FORBIDDEN.value());
            }
            return false;
        }

        private UserRecord getUserRecord(HttpServletRequest request) {
            UserService userService = applicationContext.getBean(UserService.class);
            UserRecord userRecord = (UserRecord) request.getSession().getAttribute(OAuth2LoginConfig.CURRENT_USER);
            Boolean active = userService.isActive(userRecord);
            Boolean superUser = userService.isSuperUser(userRecord);
            if (userRecord != null) {
                userRecord.setActive(active);
                userRecord.setSuperUser(superUser);
            }
            request.getSession().setAttribute(OAuth2LoginConfig.CURRENT_USER, userRecord);
            return userRecord;
        }

    }


    class DateWebBindingInitializer implements WebBindingInitializer {

        @Override
        public void initBinder(WebDataBinder webDataBinder) {
            webDataBinder.setAutoGrowCollectionLimit(AUTO_GROW_COLLECTION_LIMIT);
            webDataBinder.registerCustomEditor(Date.class,
                    new CustomDateEditor(new SimpleDateFormat(DateUtil.DATE_FORMAT), true));
        }

    }

}
